package ms.maplestory.boot.connection;

import ms.maplestory.boot.ServerConstants;
import ms.maplestory.boot.connection.opcode.OutHeader;
import ms.maplestory.boot.util.FileTime;
import ms.maplestory.boot.util.Position;
import ms.maplestory.boot.util.Rect;
import ms.maplestory.boot.util.Util;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.Arrays;

@Slf4j
public class OutPacket extends Packet {

    private ByteBuf bytebuf;
    private boolean loopback = false;
    private boolean encryptedByShanda = false;
    private short opCode;

    public OutPacket() {
        super(new byte[]{});
        bytebuf = ByteBufAllocator.DEFAULT.buffer();
    }

    public OutPacket(short opCode) {
        super(new byte[]{});
        bytebuf = PooledByteBufAllocator.DEFAULT.buffer();
        encodeShort(opCode);
        this.opCode = opCode;
    }

    public OutPacket(int opCode) {
        this((short) opCode);
    }

    public OutPacket(byte[] data) {
        super(data);
        bytebuf = ByteBufAllocator.DEFAULT.buffer();
        encodeArr(data);
    }

    public OutPacket(OutHeader header) {
        this(header.getOpcode());
    }

    @Override
    public int getHeader() {
        return opCode;
    }

    public void encodeByte(int b) {
        encodeByte((byte) b);
    }

    public void encodeByte(byte b) {
        bytebuf.writeByte(b);
    }

    public void encodeByte(boolean b) {
        bytebuf.writeBoolean(b);
    }

    public void encodeArr(byte[] bArr) {
        bytebuf.writeBytes(bArr);
    }

    public void encodeArr(String arr) {
        encodeArr(Util.getByteArrayByString(arr));
    }

    public void encodeChar(char c) {
        bytebuf.writeByte(c);
    }

    public void encodeShort(int value) {
        encodeShort((short) value);
    }

    public void encodeShort(short s) {
        bytebuf.writeShortLE(s);
    }

    public void encodeShortBE(short s) {
        bytebuf.writeShort(s);
    }

    public void encodeInt(int i) {
        bytebuf.writeIntLE(i);
    }

    public void encodeIntBE(int i) {
        bytebuf.writeInt(i);
    }

    public void encodeLong(long l) {
        bytebuf.writeLongLE(l);
    }

    public void encodeString(String name, int length) {
        encodeString(name, (short) length);
    }

    public void encodeString(String str) {
        if (str == null) {
            str = "";
        }
        if (str.length() > Short.MAX_VALUE) {
            log.error("Tried to encode a string that is too big.");
            return;
        }
        byte[] ret = str.getBytes(ServerConstants.CHARSET);
        encodeShort((short) ret.length);
        encodeString(str, (short) ret.length);
    }

    public void encodeString(String str, short length) {
        if (str == null) {
            str = "";
        }
        byte[] ret = str.getBytes(ServerConstants.CHARSET);
        if (ret.length > length) {
            ret = str.substring(0, length).getBytes(ServerConstants.CHARSET);
        }
        encodeArr(ret);
        for (int i = ret.length; i < length; i++) {
            encodeByte((byte) 0);
        }
    }

    public void encodeFT(FileTime fileTime) {
        if (fileTime == null) {
            encodeLong(0);
        } else {
            fileTime.encode(this);
        }
    }

    public void encodePosition(Position position) {
        if (position != null) {
            encodeShort(position.getX());
            encodeShort(position.getY());
        } else {
            encodeShort(0);
            encodeShort(0);
        }
    }

    public void encodeRectInt(Rect rect) {
        encodeInt(rect.getLeft());
        encodeInt(rect.getTop());
        encodeInt(rect.getRight());
        encodeInt(rect.getBottom());
    }

    public void encodePositionInt(Position position) {
        encodeInt(position.getX());
        encodeInt(position.getY());
    }

    public void encodeFT(long currentTime) {
        encodeFT(new FileTime(currentTime));
    }

    public void encodeTime(boolean dynamicTerm, int time) {
        encodeByte(dynamicTerm);
        encodeInt(time);
    }

    public void encodeTime(int time) {
        encodeByte(false);
        encodeInt(time);
    }

    public void encodeFT(LocalDateTime localDateTime) {
        encodeFT(FileTime.fromDate(localDateTime));
    }

    public void encode(Encodable encodable) {
        encodable.encode(this);
    }

    public void release() {
        super.release();
        bytebuf.release();
    }

    @Override
    public void setData(byte[] nD) {
        super.setData(nD);
        bytebuf.clear();
        encodeArr(nD);
    }

    @Override
    public byte[] getData() {
        if (bytebuf.hasArray()) {
            return bytebuf.array();
        } else {
            byte[] arr = new byte[bytebuf.writerIndex()];
            bytebuf.nioBuffer().get(arr, 0, bytebuf.writerIndex());
            return arr;
        }
    }

    @Override
    public Packet clone() {
        return new OutPacket(getData());
    }

    @Override
    public int getLength() {
        return getData().length;
    }

    public boolean isLoopback() {
        return loopback;
    }

    public boolean isEncryptedByShanda() {
        return encryptedByShanda;
    }

    @Override
    public String toString() {
        return String.format("%s, %s/0x%s\t| %s", OutHeader.getOutHeaderByOp(opCode), opCode, Integer.toHexString(opCode).toUpperCase()
                , Util.readableByteArray(Arrays.copyOfRange(getData(), 2, getData().length)));
    }

}